﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading;
using PSE.ProcessManager.Entity;
using PSE.ProcessManager.ResourceAccess;
using System.Net;
using System.Reflection;
using PSE.ProcessManager.Common.Gateway;
using PSE.ProcessManager.Common.Process;
using PSE.Framework.Configuration;

namespace PSE.ProcessManager.Server.InteligenceManager
{
    [ServiceBehavior(
     ConcurrencyMode = ConcurrencyMode.Multiple,
     InstanceContextMode = InstanceContextMode.Single)]
    //single pois ira acessar a mesmas area de memoria onde estao armazenados os dados de produto x instancia x processo
    public class InteligenceManagerService : IInteligenceManagerService, IDisposable
    {
        static InteligenceManagerService()
        {
            _instances = new Dictionary<string, List<InstanceEntity>>();
        }

        public InteligenceManagerService()
        {
            _process = InteligenceManagerProcessManager.GetInstance();
            _process.CheckIsAliveException += new EventHandler<PSE.Framework.Process.ProcessExceptionEventArgs>(_process_CheckIsAliveException);
            _process.Initialize("InteligenceManager");

            _configurationManager = new PSE.ProcessManager.ConfigurationManager.ConfigurationManager();
        }

        void _process_CheckIsAliveException(object sender, PSE.Framework.Process.ProcessExceptionEventArgs e)
        {
            Program.HandleException(new Exception(string.Format("Unable to restart the process '{0}'", e.Process.Name), e.Exception));
            e.Handled = true;
        }

        private InteligenceManagerProcessManager _process = null;
        private ConfigurationManager.ConfigurationManager _configurationManager = null;

        private static object _syncRoot = new object();
        //colecao de configuracoes de produtos x instacias e processos
        private static Dictionary<string, List<InstanceEntity>> _instances;

        public string GetInstanceAddress(string productId, int processId)
        {
            //carregar as configuracoes de produtos do arquivo
            List<InstanceEntity> instancesFromProduct = GetInstancesFromProduct(productId);
            if (instancesFromProduct != null)
            {
                //InstanceEntity instance = product.InstanceEntityCollection.First<InstanceEntity>(i => i.ProcessIds.Contains(processId));
                foreach (InstanceEntity instance in instancesFromProduct)
                {
                    if (instance.ProcessIds.Contains(processId))
                        return instance.Address.ToString();
                }
            }

            return string.Empty;
        }

        #region Products

        public bool StartProduct(string productKey)
        {
            //recuperar o full qualified host name
            string hostName = Dns.GetHostName();
            string fullHostName = Dns.GetHostEntry(hostName).HostName;


            List<InstanceEntity> instancesFromProduct = GetInstancesFromProduct(productKey, false);
            bool ret = false;

            if (instancesFromProduct != null)
            {
                //varrer a lista de instancis para criar as instancias configuradas
                foreach (InstanceEntity instance in instancesFromProduct)
                {
                    //verificar se o endereco configurado é um endereco que aponta para o servidor onde o servico esta sendo
                    //iniciado                    
                    if (instance.UriAddress.Host.ToLower() != "localhost" &&
                        hostName.ToUpper() != instance.UriAddress.Host.ToUpper() &&
                        fullHostName.ToUpper() != instance.UriAddress.Host.ToUpper())
                        continue;
                    else
                    {
                        if (!_process.IsChildStarted(productKey, instance))
                        {
                            _process.StartChild(productKey, instance);
                            ret = true;
                        }
                    }
                }
            }

            return ret;
        }

        public bool StopProduct(string productKey)
        {
            //recuperar o full qualified host name
            string hostName = Dns.GetHostName();
            string fullHostName = Dns.GetHostEntry(hostName).HostName;


            List<InstanceEntity> instancesFromProduct = GetInstancesFromProduct(productKey, false);
            bool ret = false;

            if (instancesFromProduct != null)
            {
                //varrer a lista de instancis para criar as instancias configuradas
                foreach (InstanceEntity instance in instancesFromProduct)
                {
                    //verificar se o endereco configurado é um endereco que aponta para o servidor onde o servico esta sendo
                    //iniciado                    
                    if (instance.UriAddress.Host.ToLower() != "localhost" &&
                        hostName.ToUpper() != instance.UriAddress.Host.ToUpper() &&
                        fullHostName.ToUpper() != instance.UriAddress.Host.ToUpper())
                        continue;
                    else
                    {
                        if (_process.IsChildStarted(productKey, instance))
                        {
                            _process.CloseChild(productKey, instance);
                            ret = true;
                        }
                    }
                }
            }

            return ret;
        }

        public bool RestartProduct(string productKey)
        {
            this.StopProduct(productKey);

            return this.StartProduct(productKey);
        }

        public bool AddProduct(string productName)
        {
            return _configurationManager.AddProduct(productName);
        }

        public bool RemoveProduct(string productName)
        {
            return _configurationManager.RemoveProduct(productName);
        }

        public List<string> ListProducts()
        {
            return _configurationManager.ListProducts();
        }

        #endregion

        #region Instances

        public bool IsInstanceStarted(string productKey, string instanceKey)
        {
            //recuperar o full qualified host name
            string hostName = Dns.GetHostName();
            string fullHostName = Dns.GetHostEntry(hostName).HostName;


            List<InstanceEntity> instancesFromProduct = GetInstancesFromProduct(productKey, false);

            if (instancesFromProduct != null)
            {
                //varrer a lista de instancis para criar as instancias configuradas
                foreach (InstanceEntity instance in instancesFromProduct)
                {
                    //verificar se o endereco configurado é um endereco que aponta para o servidor onde o servico esta sendo
                    //iniciado                    
                    if (instance.UriAddress.Host.ToLower() != "localhost" &&
                        hostName.ToUpper() != instance.UriAddress.Host.ToUpper() &&
                        fullHostName.ToUpper() != instance.UriAddress.Host.ToUpper())
                        continue;
                    else if (instance.Name == instanceKey)
                    {
                        return _process.IsChildStarted(productKey, instance);
                    }
                }
            }

            return false;
        }

        public bool StartInstance(string productKey, string instanceKey)
        {
            //recuperar o full qualified host name
            string hostName = Dns.GetHostName();
            string fullHostName = Dns.GetHostEntry(hostName).HostName;

            List<InstanceEntity> instancesFromProduct = GetInstancesFromProduct(productKey, false);

            if (instancesFromProduct != null)
            {
                //varrer a lista de instancis para criar as instancias configuradas
                foreach (InstanceEntity instance in instancesFromProduct)
                {
                    //verificar se o endereco configurado é um endereco que aponta para o servidor onde o servico esta sendo
                    //iniciado                    
                    if (instance.UriAddress.Host.ToLower() != "localhost" &&
                        hostName.ToUpper() != instance.UriAddress.Host.ToUpper() &&
                        fullHostName.ToUpper() != instance.UriAddress.Host.ToUpper())
                        continue;
                    else if (instance.Name == instanceKey)
                    {
                        if (!_process.IsChildStarted(productKey, instance))
                        {
                            _process.StartChild(productKey, instance);
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public bool StopInstance(string productKey, string instanceKey)
        {
            //recuperar o full qualified host name
            string hostName = Dns.GetHostName();
            string fullHostName = Dns.GetHostEntry(hostName).HostName;


            List<InstanceEntity> instancesFromProduct = GetInstancesFromProduct(productKey, false);

            if (instancesFromProduct != null)
            {
                //varrer a lista de instancis para criar as instancias configuradas
                foreach (InstanceEntity instance in instancesFromProduct)
                {
                    //verificar se o endereco configurado é um endereco que aponta para o servidor onde o servico esta sendo
                    //iniciado                    
                    if (instance.UriAddress.Host.ToLower() != "localhost" &&
                        hostName.ToUpper() != instance.UriAddress.Host.ToUpper() &&
                        fullHostName.ToUpper() != instance.UriAddress.Host.ToUpper())
                        continue;
                    else if (instance.Name == instanceKey)
                    {
                        if (_process.IsChildStarted(productKey, instance))
                        {
                            _process.CloseChild(productKey, instance);
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public bool RestartInstance(string productKey, string instanceKey)
        {
            this.StopInstance(productKey, instanceKey);

            return this.StartInstance(productKey, instanceKey);
        }

        public bool AddInstance(string productName,
                                string instanceName,
                                string instanceAddress,
                                string processPath,
                                string stsAuthorityCertificateName,
                                string serviceCertificateName)
        {
            return _configurationManager.AddInstance(productName,
                                                     instanceName,
                                                     instanceAddress,
                                                     processPath,
                                                     stsAuthorityCertificateName,
                                                     serviceCertificateName);
        }

        public bool AddInstance(string productName,
                                string instanceName,
                                string instanceAddress,
                                string processPath,
                                string stsAuthorityCertificateName,
                                string serviceCertificateName,
                                List<InteligenceModuleEntity> inteligenceModules)
        {
            return _configurationManager.AddInstance(productName,
                                                     instanceName,
                                                     instanceAddress,
                                                     processPath,
                                                     stsAuthorityCertificateName,
                                                     serviceCertificateName,
                                                     inteligenceModules);
        }

        public bool RemoveInstance(string productName, string instanceName)
        {
            return _configurationManager.RemoveInstance(productName, instanceName);
        }

        public List<InstanceEntity> ListInstances(string productName)
        {
            List<InstanceEntity> instances = _configurationManager.ListInstances(productName);

            foreach (InstanceEntity instance in instances)
            {
                if (_process.IsChildStarted(productName, instance))
                    instance.ProcessId = _process.Children[productName + "_" + instance.Name].ProcessId;
            }

            return instances;
        }

        #endregion

        #region Process

        public bool IsProcessStarted(string productKey, string instanceKey, int processId)
        {
            ProcessEntity process = _configurationManager.ListProcesses(productKey, instanceKey).FirstOrDefault(p => p.Id.Value == processId);

            if (process == null)
                throw new InvalidOperationException(string.Format("Process not found. Product: '{0}', Instance: '{1}', Process Id: '{2}'.", productKey, instanceKey, processId));

            using (ServiceManagerSenderGateway gateway = new ServiceManagerSenderGateway(productKey, instanceKey))
            {
                return gateway.IsProcessRunning(processId);
            }
        }

        public bool StartProcess(string productKey, string instanceKey, int processId)
        {
            ProcessEntity process = _configurationManager.ListProcesses(productKey, instanceKey).FirstOrDefault(p => p.Id.Value == processId);

            if (process == null)
                throw new InvalidOperationException(string.Format("Process not found. Product: '{0}', Instance: '{1}', Process Id: '{2}'.", productKey, instanceKey, processId));

            if (process.InitializationType != PSE.ProcessManager.Entity.Enums.StartUpType.Automatic)
                throw new InvalidOperationException(string.Format("It's not possible to start process with 'Manual' initialization type. Product: '{0}', Instance: '{1}', Process Id: '{2}'.", productKey, instanceKey, processId));

            using (ServiceManagerSenderGateway gateway = new ServiceManagerSenderGateway(productKey, instanceKey))
            {
                if (!gateway.IsProcessRunning(processId))
                {
                    gateway.SendMessage(new BaseMessage() { Operation = null, ProcessId = process.Id.Value });

                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public bool StopProcess(string productKey, string instanceKey, int processId)
        {
            ProcessEntity process = _configurationManager.ListProcesses(productKey, instanceKey).FirstOrDefault(p => p.Id.Value == processId);

            if (process == null)
                throw new InvalidOperationException(string.Format("Process not found. Product: '{0}', Instance: '{1}', Process Id: '{2}'.", productKey, instanceKey, processId));

            using (ServiceManagerSenderGateway gateway = new ServiceManagerSenderGateway(productKey, instanceKey))
            {
                if (gateway.IsProcessRunning(processId))
                {
                    gateway.SendMessage(new BaseMessage() { Operation = "Close", ProcessId = process.Id.Value });

                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public bool AddProcess(string productName, string instanceName, ProcessEntity process)
        {
            return _configurationManager.AddProcess(productName, instanceName, process);
        }

        public bool RemoveProcess(string productName, string instanceName, int processId)
        {
            return _configurationManager.RemoveProcess(productName, instanceName, processId);
        }

        public List<ProcessEntity> ListProcesses(string productName, string instanceName)
        {
            List<ProcessEntity> processes = _configurationManager.ListProcesses(productName, instanceName);

            if (processes.Count > 0)
            {
                using (ServiceManagerSenderGateway gateway = new ServiceManagerSenderGateway(productName, instanceName))
                {
                    foreach (ProcessEntity process in processes)
                    {
                        process.ProcessId = gateway.GetProcessPid((int)process.Id);
                    }
                }
            }

            return processes;
        }

        #endregion

        public List<InteligenceModuleEntity> ListInteligenceModules()
        {
            return _configurationManager.ListInteligenceModules();
        }

        private List<InstanceEntity> GetInstancesFromProduct(string productId)
        {
            return GetInstancesFromProduct(productId, true);
        }

        private List<InstanceEntity> GetInstancesFromProduct(string productId, bool useCache)
        {
            lock (_syncRoot)
            {
                List<InstanceEntity> ret = null;

                if (!_instances.ContainsKey(productId) || !useCache)
                {
                    List<InstanceEntity> instancesFromProduct = InstanceConfigurationResourceAccessFactory.CreateProvider().RetrieveInstances(productId);

                    if (instancesFromProduct != null)
                    {
                        string fullHostName = Dns.GetHostEntry(Dns.GetHostName()).HostName;

                        // Adiciona na lista de instance process todas as instancias que ainda não existem
                        foreach (InstanceEntity instance in instancesFromProduct)
                        {
                            ///trocar o endereço das instancias configuradas como localhost para conter o endereco do
                            ///servidor no qual o process manager esta sendo executado
                            if (instance.UriAddress.Host.Equals("localhost", StringComparison.CurrentCultureIgnoreCase))
                            {
                                UriBuilder uri = new UriBuilder(instance.Address);
                                uri.Host = fullHostName;
                                instance.Address = uri.ToString();
                            }
                        }

                        if (_instances.ContainsKey(productId))
                        {
                            _instances[productId] = instancesFromProduct;
                        }
                        else
                        {
                            _instances.Add(productId, instancesFromProduct);
                        }

                        ret = _instances[productId];
                    }
                }
                else
                {
                    ret = _instances[productId];
                }

                return ret;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            //foreach (List<InteligenceManagerInstanceProcess> processes in _instances.Values)
            //    foreach (InteligenceManagerInstanceProcess process in processes)
            //        process.Dispose();
            _process.Dispose();
            _instances.Clear();
        }

        #endregion
    }
}